#region File Description
//-----------------------------------------------------------------------------
// GameplayScreen.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using Statements
using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using ShootThemUp.Sources.ScreenManagement;
using ShootThemUp.Sources.GameElements.Ships.Player;
using ShootThemUp.Sources.PlayerInputs;
using ShootThemUp.Sources.GameElements.Background;
using ShootThemUp.Sources.PlayerInputs.WindowsPhone;
using ShootThemUp.Sources.Configuration;
using Microsoft.Xna.Framework.Media;
using ShootThemUp.Sources.GameElements.Weapons;
using System.Collections.Generic;
using ShootThemUp.Sources.GameElements.Ships.Ennemies.Waves;
using ShootThemUp.Sources.GameElements.Ships.Ennemies.Fruits;
using ShootThemUp.Sources.Factories;
using Microsoft.Xna.Framework.Input.Touch;
using ShootThemUp.Sources.AudioElements;
using System.Diagnostics;
using ShootThemUp.Sources.GameElements.Animations;
using ShootThemUp.Sources.GameElements.Ships;
#endregion

namespace ShootThemUp.Sources.Screens
{
    /// <summary>
    /// This screen implements the actual game logic. It is just a
    /// placeholder to get the idea across: you'll probably want to
    /// put some more interesting gameplay in here!
    /// </summary>
    class GameplayScreen : GameScreen
    {
        #region Constant Fields

        /// <summary>
        /// The path of the spritesheet to use.
        /// </summary>
        private const string SPRITESHEET_PATH = @"Game\Textures2D\spritesheet";
        /// <summary>
        /// The path of the background texture to use.
        /// </summary>
        private const string BACKGROUND_PATH = @"Game\Textures2D\background";
        /// <summary>
        /// The path of the spriteSheet explosion
        /// </summary>
        private const string SPRITESHEET_EXPLOSION_PATH = @"Game\Textures2D\spriteSheetexplosion";
        /// <summary>
        /// the path of the font
        /// </summary>
        private const string FONT_PATH = @"Fonts\menufont";
        /// <summary>
        /// The factor of the speed of the scrolling background.
        /// </summary>
        private const int SCROLLING_BACKGROUND_SPEED_FACTOR = 50;
        /// <summary>
        /// If playerShip.Life > 0, it is a live.
        /// Between 0 and DEATH_LIMIT, it can't move and shot, but there are a death animation.
        /// After DEATH_LIMIT, the game is over.
        /// </summary>
        private const int DEATH_LIMIT = -40;
        private const int MaxZero = 8;

        #endregion

        #region Fields

        /// <summary>
        /// The ship of the player.
        /// </summary>
        private PlayerShip playerShip;
        /// <summary>
        /// All the shot in the game are in this list.
        /// </summary>
        private List<Shot> shots;
        /// <summary>
        /// All the waves of the current game, each waves contains several ennemies fruits.
        /// </summary>
        private List<Wave> waves;
        /// <summary>
        /// The current animations. Animations are graphical elements like explosions or special effects.
        /// </summary>
        private List<Animation> animations;
        /// <summary>
        /// The player inputs, depending of the platform used.
        /// </summary>
        private PlayerInput inputs;
        private InputState inputState;
        /// <summary>
        /// The scrolling background, his speed will depend of the instanciation.
        /// </summary>
        private ScrollingBackground scrollingBackground;
        /// <summary>
        /// The explosion of the ship
        /// </summary>
        private float pauseAlpha;
        /// <summary>
        /// The total time of the game.
        /// </summary>
        private int totalTime = 0;
        /// <summary>
        /// This boolean is usefull to know if a player is death, and animate is death.
        /// </summary>
        private bool playerIsDead = false;
        private SpriteFont font;
        private int currentWave = 1;

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor.
        /// </summary>
        public GameplayScreen()
        {

            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);

            #region Inputs Attribution

#if WINDOWS_PHONE

            if (PlayerConfiguration.MovementType.Equals(MovementType.Accelerometer))
                this.inputs = new AccelerometerInput();
            else if (PlayerConfiguration.MovementType.Equals(MovementType.Touchscreen))
                this.inputs = new TouchScreenInput();

            TouchPanel.EnabledGestures = GestureType.DoubleTap;
#endif

#if WINDOWS
            //Windows version is not implemented yet.
#endif

#if XBOX
            //Xbox version is not implemented yet.
#endif

            #endregion

            // Player initialisation
            this.playerShip = new PlayerShip(PlayerShip.DEFAULT_LIFE, new Weapon(PlayerShip.DEFAULT_WEAPON, PlayerShip.DEFAULT_RELOAD_TIME, PlayerShip.DEFAULT_BALL_SHOT_NUMBER));
            //this.playerShip = new PlayerShip(PlayerShip.DEFAULT_LIFE, new Weapon(PlayerShip.DEFAULT_WEAPON, PlayerShip.DEFAULT_RELOAD_TIME, 51));
            //this.playerShip = new PlayerShip(PlayerShip.DEFAULT_LIFE, new Weapon(WeaponType.MultiSimpleShot, PlayerShip.DEFAULT_RELOAD_TIME, PlayerShip.DEFAULT_SIMPLE_SHOT_NUMBER));

            // Fruits initialisation
            this.waves = LevelFactory.Level1(this.currentWave);

            //Shots initialisation
            this.shots = new List<Shot>();

            // Background initialisation
            this.scrollingBackground = new ScrollingBackground();

            // Animations initialisation
            this.animations = new List<Animation>();

            MediaPlayer.Stop();

        }

        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            if (content == null)
                content = new ContentManager(ScreenManager.Game.Services, "Content");

            // Player loading
            this.playerShip.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);

            // Fruits loading
            foreach (Wave wave in this.waves)
                wave.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);

            // Background loading
            Texture2D background = content.Load<Texture2D>(GameplayScreen.BACKGROUND_PATH);
            this.scrollingBackground.Load(ScreenManager.GraphicsDevice, background);

            this.font = content.Load<SpriteFont>(FONT_PATH);

            // giving you a chance to admire the beautiful loading screen.
            Thread.Sleep(1000);

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            ScreenManager.Game.ResetElapsedTime();
        }


        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            content.Unload();
        }

        #endregion

        #region Update, Draw, Handler

        /// <summary>
        /// Updates the state of the game. This method checks the GameScreen.IsActive
        /// property, so the game will stop updating when the pause menu is active,
        /// or if you tab away to a different application.
        /// </summary>
        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            if (coveredByOtherScreen)
                pauseAlpha = Math.Min(pauseAlpha + 1f / 32, 1);
            else
                pauseAlpha = Math.Max(pauseAlpha - 1f / 32, 0);

            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
            

            if (IsActive)
            {
                totalTime += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
                
                this.DetectShotsCollisions();
                this.DetectFruitsCollision();

                Audio audio = Audio.GetInstance;

                #region Player ship update

                #region Player Speed Update

                //We update the speed of the player ship with the speed given by the inputs method (accel, touch screen ...)
                if (this.inputs is AccelerometerInput && !playerIsDead)
                {
                    this.playerShip.Speed = inputs.Speed();
                }
                else if (this.inputs is TouchScreenInput && !playerIsDead)
                {
                    TouchScreenInput tsi = (TouchScreenInput)this.inputs;
                    this.playerShip.Speed = tsi.Speed(this.playerShip.Position, this.playerShip.SourceRectangle.Value.Width, this.playerShip.SourceRectangle.Value.Height, gameTime.ElapsedGameTime.Milliseconds);
                }
                else if (playerIsDead)
                {
                    this.playerShip.Speed = Vector2.Zero;
                }

                #endregion

                #region Player Shots

                if (!playerIsDead && this.playerShip.CanShoot() && (inputs.IsShooting() || PlayerConfiguration.AutoShot))
                {
                    List<Shot> newShots = ShotFactory.CreateShots(playerShip);

                    foreach (Shot shot in newShots)
                    {
                        shot.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);
                        this.shots.Add(shot);
                    }

                    if (PlayerConfiguration.SoundEffect && playerShip.Weapon.WeaponType.Equals(WeaponType.MultiBallShot))
                    {
                        audio.PlayerBall.Play(Audio.SOUNDEFFECT_VOLUME, 0, 0);
                    }
                    else if (PlayerConfiguration.SoundEffect && playerShip.Weapon.WeaponType.Equals(WeaponType.MultiSimpleShot))
                    {
                        audio.PlayerPew.Play(Audio.SOUNDEFFECT_VOLUME, 0, 0);
                    }

                    playerShip.ShotDone();
                }

                #endregion

                #region Player Swapping Weapons

                if (this.inputState != null && inputs.SwappingWeapon(this.inputState) && !playerIsDead)
                {
                    if (playerShip.Weapon.WeaponType == WeaponType.MultiBallShot)
                    {
                        playerShip.Weapon.WeaponType = WeaponType.MultiSimpleShot;
                        playerShip.Weapon.NumberOfShot = PlayerShip.DEFAULT_SIMPLE_SHOT_NUMBER;
                        playerShip.Weapon.ShotTexture = null;
                    }
                    else if (playerShip.Weapon.WeaponType == WeaponType.MultiSimpleShot)
                    {
                        playerShip.Weapon.WeaponType = WeaponType.MultiBallShot;
                        playerShip.Weapon.NumberOfShot = PlayerShip.DEFAULT_BALL_SHOT_NUMBER;
                        playerShip.Weapon.ShotTexture = null;
                    }
                }

                this.playerShip.Update(gameTime);

                #endregion

                #region Player Death

                if (playerShip.Life <= 0 && !playerIsDead)
                {
                    playerIsDead = true;
                    Explosion explosion = new Explosion(playerShip.Position);
                    explosion.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);
                    this.animations.Add(explosion);
                    audio.Explosion.Play();
                }

                if (playerShip.Life <= GameplayScreen.DEATH_LIMIT)
                    ScreenManager.AddScreen(new PlayerDeathScreen(playerShip.Score), ControllingPlayer);

                if (playerIsDead)
                    playerShip.Life -= 1;

                #endregion

                #endregion

                #region Shots update

                for (int i = 0; i < this.shots.Count; i++)
                {
                    Shot shot = this.shots[i];

                    if (shot.Position.X + shot.SourceRectangle.Value.Width < 0 || shot.Position.X > GameStateManagement.DEFAULT_PORTRAIT_SCREEN_WIDTH ||
                        shot.Position.Y + shot.SourceRectangle.Value.Height < 0 || shot.Position.Y > GameStateManagement.DEFAULT_PORTRAIT_SCREEN_HEIGHT)
                    {
                        this.shots.Remove(shot);
                        i--;
                    }

                    shot.Update(gameTime);
                }

                #endregion

                #region Fruits update

                // Endless mode
                if (this.totalTime > this.currentWave * LevelFactory.LEVEL1_DURATION)
                {
                    this.currentWave++;
                    this.waves = LevelFactory.Level1(this.currentWave);
                    foreach (Wave wave in waves)
                        wave.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);
                }

                foreach (Wave wave in this.waves)
                {
                    List<Animation> newAnimations = wave.Update(totalTime, gameTime);

                    foreach (Animation animation in newAnimations)
                    {
                        animation.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);
                        animations.Add(animation);
                    }
                    
                    for (int i = 0; i < wave.Fruits.Count; i++)
                    {
                        if (wave.Fruits[i].Life < 0)
                        {
                            FruitType type = (wave.Fruits[i] is Cherry) ? FruitType.Cherry : (wave.Fruits[i] is Pear) ? FruitType.Pear : FruitType.Orange;
                            FruitExplosion explosion = new FruitExplosion(wave.Fruits[i].Position, type);
                            explosion.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);
                            this.animations.Add(explosion);
                            this.playerShip.Score += wave.Fruits[i].ScoreValue;
                            wave.Fruits.RemoveAt(i);
                            i--;
                        }
                        else if (wave.Fruits[i].CanShoot())
                        {
                            List<Shot> newShots = ShotFactory.CreateShots(wave.Fruits[i]);

                            foreach (Shot shot in newShots)
                            {
                                shot.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);
                                this.shots.Add(shot);
                            }

                            wave.Fruits[i].ShotDone();
                        }
                    }
                }

                #endregion

                #region Animations Update

                foreach (Animation animation in this.animations)
                {
                    animation.Update(gameTime);
                }

                #endregion

                #region Audio update

                if (MediaPlayer.State != MediaState.Playing && audio.Game != null)
                {
                    MediaPlayer.Volume = Audio.SONG_VOLUME;
                    MediaPlayer.Play(audio.Game);
                }

                #endregion

                #region Background update

                this.scrollingBackground.Update((float)gameTime.ElapsedGameTime.TotalSeconds * GameplayScreen.SCROLLING_BACKGROUND_SPEED_FACTOR);

                #endregion
            }
        }


        /// <summary>
        /// Lets the game respond to player input. Unlike the Update method,
        /// this will only be called when the gameplay screen is active.
        /// </summary>
        public override void HandleInput(InputState input)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            this.inputState = input;

            // Look up inputs for the active player profile.
            int playerIndex = (int)ControllingPlayer.Value;

            // if the user pressed the back button, we return to the main menu
            PlayerIndex player;
            if (input.IsNewButtonPress(Buttons.Back, ControllingPlayer, out player))
            {
                AccelerometerInput ai = null;

                // If it's on windows phone, we stop the accelerometer during the pause
                if (playerShip != null && this.inputs is AccelerometerInput)
                {
                    ai = (AccelerometerInput)this.inputs;
                    ai.StopAccelerometer();
                }

                ScreenManager.AddScreen(new PauseScreen(ai), ControllingPlayer);
            }
        }


        /// <summary>
        /// Draws the gameplay screen.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
            // Our player and enemy are both actually just text strings.
            SpriteBatch spriteBatch = ScreenManager.SpriteBatch;

            spriteBatch.Begin();

            //Firstly, we draw the background
            this.scrollingBackground.Draw(spriteBatch);

            //Then, we draw the game entities
            //Player drawing
            if(!playerIsDead)
                this.playerShip.Draw(spriteBatch);

            //Fruits drawing
            foreach (Wave wave in this.waves)
                wave.Draw(spriteBatch, totalTime);

            foreach (Shot shot in this.shots)
                shot.Draw(spriteBatch);

            foreach (Animation animation in this.animations)
                animation.Draw(spriteBatch);

            int size = this.playerShip.Score.ToString().Length;

            string msg = "score : ";

            for (int i = 0; i < (GameplayScreen.MaxZero - size); i++)
            {
                msg += "0";
            }

            spriteBatch.DrawString(font, msg + this.playerShip.Score, new Vector2(270, 0), Color.Black); 
            spriteBatch.End();

            // If the game is transitioning on or off, fade it out to black.
            if (TransitionPosition > 0)
                ScreenManager.FadeBackBufferToBlack(1f - TransitionAlpha);
        }

        #endregion

        #region Collisions Methods

        /// <summary>
        /// Detects the collisions between shots and ships, and done methods in function of the collisions.
        /// </summary>
        private void DetectShotsCollisions()
        {
            for (int i = 0; i < this.shots.Count; i++)
            {
                Shot shot = this.shots[i];
                bool shotTouch = false;

                if (!shot.IsEnnemyShot)
                {
                    // Fruit Collisions
                    foreach (Wave wave in this.waves)
                    {
                        if (shotTouch)
                            break;

                        foreach (Fruit fruit in wave.Fruits)
                        {
                            Rectangle shotHitBox = (Rectangle)shot.Hitbox;
                            Rectangle fruitHitBox = (Rectangle)fruit.Hitbox;

                            if (shotHitBox.Intersects(fruitHitBox))
                            {
                                this.Impact(shot, fruit);
                                i--;
                                shotTouch = true;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    if (!playerIsDead)
                    {
                        // PlayerShip Collisions
                        Rectangle shotHitBox = (Rectangle)shot.Hitbox;
                        Rectangle playerHitBox = (Rectangle)playerShip.Hitbox;

                        if (shotHitBox.Intersects(playerHitBox))
                        {
                            this.Impact(shot, playerShip);
                            i--;
                        }
                    }
                }
            }
        }

        private void Impact(Shot shot, Ship target)
        {
            target.Life -= shot.Damages;
            this.shots.Remove(shot);

            LittleExplosion littleAnimation = new LittleExplosion(shot.Position);
            littleAnimation.LoadContent(content, GameplayScreen.SPRITESHEET_PATH);
            this.animations.Add(littleAnimation);
        }

        /// <summary>
        /// Detects the collision between the fruits and the player ship.
        /// If there are an intersection between the playership and a fruit, the player die in an acidifying and fresh juice.
        /// </summary>
        private void DetectFruitsCollision()
        {
            foreach (Wave wave in this.waves)
                foreach (Fruit fruit in wave.Fruits)
                {
                    if (!playerIsDead)
                    {
                        Rectangle fruitHitBox = (Rectangle)fruit.Hitbox;
                        Rectangle playerHitBox = (Rectangle)playerShip.Hitbox;

                        if (playerHitBox.Intersects(fruitHitBox))
                            playerShip.Life = 0;
                    }
                }
        }

        #endregion
    }
}
